//
// Copyright (c) 2009 All Right Reserved
//
// vl
//
// 2009-01-01
// Contains ...
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Xml.Linq;
using LargoCommon.Abstract;
using LargoCommon.Localization;
using LargoCommon.Music;
namespace LargoCommon.Support
{
///
/// Music Xml.
///
public sealed class MusicXmlReader
{
#region Fields
///
/// Musical Block.
///
private MusicalBlock musicalBlock;
///
/// The linearizer
///
private MusicalLinearizer linearizer;
#endregion
#region Constructors
#endregion
#region Properties
///
/// Gets Musical Block.
///
/// Property description.
///
/// Gets musical block.
///
/// Property description.
public MusicalBlock Block
{
get
{
Contract.Ensures(Contract.Result() != null);
if (this.musicalBlock == null) {
throw new InvalidOperationException("Musical block is null.");
}
return this.musicalBlock;
}
private set => this.musicalBlock = value ?? throw new ArgumentException("Argument cannot be null.", nameof(value));
}
///
/// Gets Common Division.
///
/// Property description.
public int CommonDivision { get; private set; }
///
/// Gets Local Division.
///
/// Property description.
public int LocalDivision { get; private set; }
///
/// Gets or sets Local Pitch Shift.
///
/// Property description.
public int LocalPitchShift { get; set; }
///
/// Gets or sets Header.
///
private MusicXmlHeader Header { get; set; }
///
/// Gets or sets Musical File.
///
/// Property description.
private MusicalBundle MusicalBundle { get; set; }
#endregion
#region Music Xml
///
/// Read MusicXml file..
///
/// Musical file path.
/// Name of the internal.
///
/// Returns value.
///
public static MusicalBundle ReadMusicXmlFile(string path, string internalName)
{
Contract.Requires(path != null);
if (string.IsNullOrEmpty(path)) {
return null;
}
//// string fileName = Path.GetFileNameWithoutExtension(path);
//// WinAbstract logger = this.Window.EditorWindow.OpenWindow("InherentLogger", null);
var musicXmlReader = new MusicXmlReader();
var musicXmlDocument = XDocument.Load(path);
ProcessLogger.Singleton.SendLogEvent(Path.GetFileName(path), LocalizedMusic.String("Reading MusicXml file ... "), 0);
var musicalBundle = musicXmlReader.ExtractMusicalFile(musicXmlDocument, path, internalName);
//// musicalBundle.MidFileId = midiFile.Id;
if (musicalBundle == null) {
return null;
}
//// string name = Path.GetFileNameWithoutExtension(path);
musicalBundle.FileName = internalName.ClearSpecialChars(); //// name ?? "Unknown";
return musicalBundle;
}
#endregion
#region Music Mxl
///
/// Extract Musical File from the Music Xml.
///
/// Musical file path.
/// Name of the internal.
///
/// Returns value.
///
public static MusicalBundle ReadMusicMxlFile(string path, string internalName)
{
Contract.Requires(path != null);
Contract.Requires(path.Length != 0);
if (string.IsNullOrEmpty(path)) {
return null;
}
ProcessLogger.Singleton.SendLogEvent(path, LocalizedMusic.String("Reading MusicMxl file ... "), 0);
var tempFolder = Path.GetTempPath();
var subfolder = Guid.NewGuid().ToString();
var subfolderPath = Path.Combine(tempFolder, subfolder);
if (string.IsNullOrEmpty(subfolderPath)) {
return null;
}
Directory.CreateDirectory(subfolderPath);
ZipFile.ExtractToDirectory(path, subfolderPath);
//// ZipFileCover.UnzipFile(path, subfolderPath);
var fi = SupportFiles.LatestFile(subfolderPath, "*.xml");
if (fi == null) {
return null;
}
var musicXmlReader = new MusicXmlReader();
var musicXmlDocument = XDocument.Load(fi.FullName);
var musicalBundle = musicXmlReader.ExtractMusicalFile(musicXmlDocument, path, internalName);
if (musicalBundle == null) {
return null;
}
//// string name = Path.GetFileNameWithoutExtension(path);
musicalBundle.FileName = internalName.ClearSpecialChars(); //// name ?? "Unknown";
return musicalBundle;
}
#endregion
///
/// Determines the rhythmic order.
///
/// The division.
/// Returns value.
private static int DetermineRhythmicOrder(int division)
{
const int p2 = 2;
const int p3 = 3;
const int p5 = 5;
while (division > DefaultValue.MaximumRhythmicOrder) {
if (division % p2 == 0) {
division = division / p2;
}
else {
if (division % p3 == 0) {
division = division / p3;
}
else {
if (division % p5 == 0) {
division = division / p5;
}
}
}
}
return division;
}
#region Private methods
///
/// Extract Musical File from the Music Xml.
///
/// Xml document.
/// The path.
/// Name of the internal.
///
/// Returns value.
///
private MusicalBundle ExtractMusicalFile(XDocument musicXmlDocument, string path, string internalName)
{
//// const int rhythmicDivisionLimit = 250;
Contract.Requires(musicXmlDocument != null);
if (musicXmlDocument == null) {
return null;
}
var name = internalName.ClearSpecialChars();
var justPath = Path.GetDirectoryName(path);
this.Block = new MusicalBlock
{
Header = new MusicalHeader()
{
Name = name,
//// FilePath = Path.Combine(justPath ?? throw new InvalidOperationException(), name),
FileName = name,
System = { HarmonicOrder = DefaultValue.HarmonicOrder },
Tempo = DefaultValue.DefaultTempo,
}
};
this.MusicalBundle = MusicalBundle.GetEnvelopeOfBlock(this.Block, string.Empty);
this.MusicalBundle.OriginType = MusicalOriginType.Original;
//// Score Partwise
var scorePartwise = musicXmlDocument.Root; //// element name ="score-partwise"
this.Header = new MusicXmlHeader(scorePartwise, this.Block);
this.Header.ReadXmlHeader();
//// ProcessLogger.Singleton.SendLogEvent(LocalizedMusic.String("Preparing file..."), 0);
this.CommonDivision = 1;
if (musicXmlDocument.Root != null) {
var parts = musicXmlDocument.Root.Elements("part");
var plist = parts as IList ?? parts.ToList(); //// resharper
foreach (var part in plist) {
this.ReadMusicalDivision(part);
}
var d = DetermineRhythmicOrder(this.CommonDivision);
this.Block.Header.System.RhythmicOrder = (byte)d; //// this.MusicalBlock.Division
this.Block.Header.Division = this.CommonDivision;
//// if (this.MusicalBlock.Division > rhythmicDivisionLimit) {
//// If rhythm coded in arithmetic of 3-symbols then limit is 523
//// throw new ArgumentException("Rhythmical order can not exceed " + rhythmicDivisionLimit + " !!!"); }
this.AppendParts(plist);
}
this.linearizer.TransferPartsToTracks(true);
var context = new MusicalContext(MusicalSettings.Singleton, this.Block.Header);
this.Block.Strip = new MusicalStrip(context);
foreach (var t in this.linearizer.Lines) {
this.Block.AddLine(t);
}
this.Block.LoadFirstStatusToLines(); //// 2019/10
this.Block.ConvertStripToBody(false);
this.Block.Body.SetBodyStatusFromTones();
return this.MusicalBundle;
}
///
/// Read Musical Part.
///
/// Musical part.
private void ReadMusicalDivision(XContainer part)
{
Contract.Requires(part != null);
var measures = part.Elements("measure");
//// int barNumber = 0;
foreach (var measure in measures) {
//// byte numberOfStaves = 1;
//// barNumber++;
var attributes = measure.Element("attributes");
if (attributes == null) {
continue;
}
var time = attributes.Element("time");
if (time != null) {
var symbol = (string)time.Attribute("symbol");
if (string.CompareOrdinal(symbol, "cut") == 0) {
this.Block.Header.Metric.MetricBeat = 4;
this.Block.Header.Metric.MetricBase = 2;
}
else {
var b = time.Element("beats");
if (b != null && !b.IsEmpty) {
this.Block.Header.Metric.MetricBeat = (byte)(int)b;
}
var bt = time.Element("beat-type");
if (bt != null && !bt.IsEmpty) {
var metricGround = (byte)(int)bt;
this.Block.Header.Metric.MetricBase = MusicalProperties.GetMetricBase(metricGround);
}
}
}
var divisions = attributes.Element("divisions");
if (divisions == null) {
continue;
}
var beatDivision = (int)divisions;
this.LocalDivision = this.Block.Header.Metric.MetricBeat * beatDivision;
this.CommonDivision = (int)MathSupport.LeastCommonMultiple(this.CommonDivision, this.LocalDivision);
//// string staves = (string)attributes.Element("staves");
//// if (staves != null) { numberOfStaves = byte.Parse(staves); }
}
}
///
/// Read Musical Part.
///
/// Musical part.
/// Line number.
/// Returns value.
private MusicalPart ReadMusicalPart(XElement part, int lineIndex)
{
Contract.Requires(part != null);
var partId = (string)part.Attribute("id");
var scorePartObject = this.Header.ScoreParts[partId];
var musicalPart = MusicalPart.GetNewMusicalPart(this.Block, lineIndex, scorePartObject.MidiChannel);
musicalPart.PartId = partId;
musicalPart.Purpose = LinePurpose.Fixed; //// 2019/01 LinePurpose.Fixed;
var measures = part.Elements("measure");
var barNumber = 0;
foreach (var measure in measures) {
barNumber++;
var attributes = measure.Element("attributes");
var divisions = attributes?.Element("divisions");
if (divisions != null) {
var beatDivision = (int)divisions;
this.LocalDivision = this.Block.Header.Metric.MetricBeat * beatDivision;
}
var musicXmlMeasure = new MusicXmlMeasure(this.Header);
musicXmlMeasure.ReadMusicalBar(scorePartObject, musicalPart, measure, barNumber, this);
}
this.Block.Header.NumberOfBars = barNumber;
musicalPart.LayObjectsToVoiceTracks();
return musicalPart;
}
///
/// Appends the parts.
///
/// The list of parts.
private void AppendParts(IEnumerable plist)
{
Contract.Requires(plist != null);
this.linearizer = new MusicalLinearizer(null); //// block.header !?!
byte partNumber = 0;
foreach (var part in plist) {
var musicalPart = this.ReadMusicalPart(part, ++partNumber);
var scorePartObject = this.Header.ScoreParts[musicalPart.PartId];
this.LocalPitchShift = 0;
if (scorePartObject != null) {
musicalPart.Instrument = new MusicalInstrument((MidiMelodicInstrument)scorePartObject.MidiProgram);
musicalPart.Channel = scorePartObject.MidiChannel;
}
//// musicalPart.MusicalBlock = this.Block;
musicalPart.Purpose = LinePurpose.Fixed; //// 2019/01 LinePurpose.Fixed
//// track.LineType = MusicalLineType.Melodic;
//// track.Status.LineType = MusicalLineType.Melodic;
this.linearizer.Parts.Add(musicalPart);
}
}
#endregion
}
}